package org.onionframework.persistence.support;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.persistence.Column;

import org.apache.commons.lang.ArrayUtils;
import org.hibernate.SessionFactory;
import org.hibernate.metadata.ClassMetadata;
import org.onionframework.core.util.Reflections;
import org.onionframework.persistence.extend.OnionAnnotationReader;
import org.onionframework.persistence.model.annotation.Property;
import org.onionframework.persistence.model.support.ExtendComponent;
import org.onionframework.persistence.util.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class EntityMetainfoManager {

	@Autowired private SessionFactory sessionFactory;
	private Map<Class<?>,Map<String,PropertyInfo>> uniqueProperties = new HashMap<Class<?>,Map<String,PropertyInfo>>();
	
	private static Map<String, Class<?>> relatedEntityClassMap = new HashMap<String, Class<?>>();
	
	/** 日志操作类 */
	private static Logger logger = LoggerFactory.getLogger(EntityMetainfoManager.class);
	
	@PostConstruct
	public void init(){
		Map<String,ClassMetadata> metaInfoMap = sessionFactory.getAllClassMetadata();
		
		for(String className : metaInfoMap.keySet())
			this.initUniqueProperties(className);
	}
	
	public Map<String,PropertyInfo> getUniqueProperties(Class<?> clazz){
		return this.uniqueProperties.get(clazz);
	}

	private void initUniqueProperties(String className) {
		try {
			Map<String,PropertyInfo> uniqueProperty = new HashMap<String,PropertyInfo>();
			
			Class<?> clazz = Class.forName(className);
			
			Class<?> stopClass = ModelUtils.getRootEntityClass(clazz);
			
			BeanInfo info = null;
			
			if(!clazz.equals(stopClass))
				info = Introspector.getBeanInfo(clazz, stopClass);
			else
				info = Introspector.getBeanInfo(clazz);
			
			PropertyDescriptor[] descriptors = info.getPropertyDescriptors();

			Method readMethod;
			Column column;
			Property property;
			String name;
			
			for(PropertyDescriptor descriptor : descriptors)
			{
				readMethod = descriptor.getReadMethod();
				if(readMethod != null){
					column = readMethod.getAnnotation(Column.class);
					property = readMethod.getAnnotation(Property.class);
					
					if(column != null && column.unique()){
						if(property != null)name = property.name();
						else name = descriptor.getName();
						
						uniqueProperty.put(descriptor.getName(), new PropertyInfo(descriptor.getName(),name,column.nullable()));
					}
				}
			}
			
			this.uniqueProperties.put(clazz, uniqueProperty);
			
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		} catch (IntrospectionException e) {
			throw new RuntimeException(e);
		}
	}
	

	
	public static Class<?> getRelatedType(Class<?> entityClass, String name) {
		
		if (relatedEntityClassMap.containsKey(entityClass.getName() + "." + name))
			return relatedEntityClassMap.get(name);

		if(name.indexOf('.') > 0){
			entityClass = getRelatedType(entityClass, name.substring(0,name.indexOf('.')));
			return getRelatedType(entityClass,name.substring(name.indexOf('.') + 1));
		}
		
		Class<?> result = null;
		Field field = Reflections.getDeclaredField(entityClass, name);
		Class<?> type = field.getType();

		if (type.equals(Collection.class)
				|| ArrayUtils.contains(type.getInterfaces(), Collection.class)) {
			try {
				java.lang.reflect.Type fieldType = field.getGenericType();
				if (fieldType instanceof ParameterizedType) {
					java.lang.reflect.Type[] params = ((ParameterizedType) fieldType)
							.getActualTypeArguments();
					if (params.length == 0)
						result = null;
					else
						result = (Class<?>) params[0];
				}
			} catch (Exception e) {
				logger.error("获取属性泛型类型失败", e);
			}
		}else if(type.equals(ExtendComponent.class)){
			result = OnionAnnotationReader.getExtendType(entityClass, name);
		}else
			result = type;

		return result;
	}
	
	public static class PropertyInfo{
		private String name;
		private String displayName;
		private boolean nullable;
		
		public PropertyInfo(String name, String displayName, boolean nullable) {
			super();
			this.name = name;
			this.displayName = displayName;
			this.nullable = nullable;
		}
		/**
		 * @return the name
		 */
		public String getName() {
			return name;
		}
		/**
		 * @param name the name to set
		 */
		public void setName(String name) {
			this.name = name;
		}
		/**
		 * @return the displayName
		 */
		public String getDisplayName() {
			return displayName;
		}
		/**
		 * @param displayName the displayName to set
		 */
		public void setDisplayName(String displayName) {
			this.displayName = displayName;
		}
		/**
		 * @return the nullable
		 */
		public boolean isNullable() {
			return nullable;
		}
		/**
		 * @param nullable the nullable to set
		 */
		public void setNullable(boolean nullable) {
			this.nullable = nullable;
		}
		
	}
}
